package com.alvin.demo.utils;

public class ModbusRTURequestBuilder {

    /**
     * @param slaveAddress 从站地址
     * @param functionCode 功能码 3 为读取操作、 6 为写入操作
     * @param startAddress 起始寄存器地址
     * @param data 寄存器数量 或者 写入的单个寄存器的数据
     * @return Modbus-RTU 协议请求报文
     */
    public static byte[] buildModbusRTURequest(
            int slaveAddress,
            int functionCode,
            int startAddress,
            int data
    ) {
        // 创建一个字节数组用于存储报文
        byte[] request = new byte[8];
        // 设置从机地址
        request[0] = (byte) slaveAddress;
        // 设置功能码
        request[1] = (byte) functionCode;
        // 设置起始寄存器地址（高位和低位）
        request[2] = (byte) (startAddress >> 8);
        request[3] = (byte) (startAddress & 0xFF);
        // 设置寄存器数量（高位和低位）
        request[4] = (byte) (data >> 8);
        request[5] = (byte) (data & 0xFF);
        // 计算CRC校验并设置到报文中
        int crc = calculateCRC(request, 6);
        request[6] = (byte) (crc & 0xFF); // 低位在前，高位在后，大端村储
        request[7] = (byte) (crc >> 8); // 低位在前，高位在后，大端村储
//        System.out.println(NumberUtils.bytesToHexString(request , 8));
        // 返回 Modbus-RTU 协议请求报文
        return request;
    }

    /**
     * @param slaveAddress 从站地址
     * @param functionCode 功能码 3 为读取操作、 6 为写入操作
     * @param startAddress 起始寄存器地址
     * @param data 寄存器数量 或者 写入的单个寄存器的数据
     * @return Modbus-RTU 协议请求报文
     */
    public static byte[] checkTime(
            int slaveAddress
    ){
        // 创建一个字节数组用于存储报文
        byte[] request = new byte[23];
        // 设置从机地址
        request[0] = (byte) slaveAddress;
        request[1] = (byte) 0x10; // 功能码 16 , 写入多个寄存器
        request[2] = (byte) 0x00; // 起始寄存器地址
        request[3] = (byte) 0x3C; // 起始寄存器地址
        request[4] = (byte) 0x00; // 写入寄存器数量
        request[5] = (byte) 0x07; // 写入寄存器数量
        request[6] = (byte) 0x0E; // 写入数量个数
        request[7] = (byte) 0x00; // 年
        request[8] = (byte) 0x14; // 年 24年
        request[9] = (byte) 0x00; // 月
        request[10] = (byte) 0x11; // 月
        request[11] = (byte) 0x00; // 日
        request[12] = (byte) 0x07; // 日
        request[13] = (byte) 0x00; // 时
        request[14] = (byte) 0x07; // 时
        request[15] = (byte) 0x00; // 分
        request[16] = (byte) 0x07; // 分
        request[17] = (byte) 0x00; // 星期
        request[18] = (byte) 0x03; // 星期
        request[19] = (byte) 0x00;  // 秒
        request[20] = (byte) 0x03;  // 秒
        // 计算CRC校验并设置到报文中
        int crc = calculateCRC(request, 21);
        request[21] = (byte) (crc & 0xFF); // 低位
        request[22] = (byte) (crc >> 8); // 高位
        System.out.println(NumberUtils.bytesToHexString(request , request.length));
        return request ;
    }

    /*
        1、预置一个 16 位寄存器为 OxFFFF（16 进制，全 1）,称之为 CRC 寄存器。
        2、把数据帧中的第一个字节的 8 位与 CRC 寄存器中的低字节进行异或运算，结果存回 CRC
           寄存器。
        3、将 CRC 寄存器向右移一位，最高位填以 0，最低位移出并检测。
        4、上一步中被移出的那一位如果为 0：重复第三步（下一次移位）；为 1：将 CRC 寄存器与
           一个预设的固定值（多项式 0A001H）进行异或运算。
        5、重复第三点和第四步直到 8 次移位。这样处理完了一个完整的八位。
        6、重复第 2 步到第 5 步来处理下一个八位，直到所有的字节处理结束。
        7、最终CRC寄存器的值就是 CRC 的值。
    */
    private static int calculateCRC(byte[] message, int length) {
        int crc = 0xFFFF;
        for (int i = 0; i < length; i++) {
            crc ^= (message[i] & 0xFF);
            for (int j = 0; j < 8; j++) {
                if ((crc & 1) == 1) {
                    crc = (crc >> 1) ^ 0xA001;
                } else {
                    crc = crc >> 1;
                }
            }
        }
        return crc;
    }

}
